/**
 * @file STL_Socket.h
 * @author Derek Liu (yjkhtddx@sina.com)
 * @brief 
 * @version 2.00.05
 * @date 2022-01-10
 * 
 * @coryright Copyright Sci-Tech Laboratories (c) 2022
 * 
 */

#ifndef __STL_SOCKET_H__
#define __STL_SOCKET_H__

#include <sys/epoll.h>
#include <sys/socket.h>
#include <functional>
#include <thread>
#include <stdio.h>
#include <errno.h>
#include <stdint.h>
#include <netinet/tcp.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <memory>
#include "STL_Utils.h"

#define EPOLL_MAX_EVENTS 10 // 单次处理事件个数
#define SOCKET_DEFAULT_BUF_SIZE (256 * 1024)
namespace STL
{
    class STL_Socket
    {
    public: // socket set
        //设置地址复用
        static int setReuseAddr(int fd_socket, bool isReuse = true)
        {
            int opt = isReuse ? 1 : 0;
            int ret = setsockopt(fd_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, static_cast<socklen_t>(sizeof(opt)));
            if (ret == -1)
            {
                stlog_debug("SO_REUSEADDR Failed");
            }
            return ret;
        };
        //设置端口复用
        static int setReusePort(int fd_socket, bool isReuse = true)
        {
            int ret = -1;
#if defined(SO_REUSEPORT)
            int opt = isReuse ? 1 : 0;
            ret = setsockopt(fd_socket, SOL_SOCKET, SO_REUSEPORT, (char *)&opt, static_cast<socklen_t>(sizeof(opt)));
            if (ret == -1)
            {
                stlog_debug("SO_REUSEADDR Failed");
            }
#endif
            return ret;
        };
        //设置非阻塞模式
        static int setNonBlock(int fd_socket)
        {
            int flags;
            flags = fcntl(fd_socket, F_GETFD);
            if (flags == -1)
            {
                stlog_debug("setNonBlock Failed");
                return -1;
            }
            flags = flags | O_NONBLOCK;
            if (fcntl(fd_socket, F_SETFL, flags) == -1)
            {
                stlog_debug("setNonBlock Failed");
                return -1;
            }
            return 0;
        };
        //设置非阻塞模式2
        static int setNonBlock(int fd_socket, bool isNonblock)
        {
#if defined(_WIN32)
            unsigned long ul = isNonblock;
#else
            int ul = isNonblock;
#endif
            int ret = ioctl(fd_socket, FIONBIO, &ul); //设置为非阻塞模式
            if (ret == -1)
            {
                stlog_debug("FIONBIO Failed");
            }
        };
        //设置非延迟模式
        static int setTcpNoDelay(int fd_socket, bool isEnable = true)
        {
            int opt = isEnable ? 1 : 0;
            int ret = setsockopt(fd_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&opt, static_cast<socklen_t>(sizeof(opt)));
            if (ret == -1)
            {
                stlog_debug("TCP_NODELAY Failed");
            }
            return ret;
        }
        //设置接收缓冲区
        static int setRecvBuf(int fd_socket, int size = SOCKET_DEFAULT_BUF_SIZE)
        {
            int ret = setsockopt(fd_socket, SOL_SOCKET, SO_RCVBUF, (char *)&size, sizeof(size));
            if (ret == -1)
            {
                stlog_debug("SO_RCVBUF failed");
            }
            return ret;
        }
        //设置发送缓冲区
        static int setSendBuf(int fd_socket, int size = SOCKET_DEFAULT_BUF_SIZE)
        {
            int ret = setsockopt(fd_socket, SOL_SOCKET, SO_SNDBUF, (char *)&size, sizeof(size));
            if (ret == -1)
            {
                stlog_debug("SO_SNDBUF failed");
            }
            return ret;
        }
        //设置关闭等待 [linger vi.徘徊;继续存留;缓慢消失;流连;逗留;花很长时间做(某事);磨蹭;持续看(或思考);苟延残喘]
        static int setLinger(int fd_socket, int second = 0)
        {
            linger m_sLinger;
            m_sLinger.l_onoff = (second > 0); //  为0时,closesocket()后强制关闭
            m_sLinger.l_linger = second;      // closesocket()时，等待的秒数
            int ret = setsockopt(fd_socket, SOL_SOCKET, SO_LINGER, (char *)&m_sLinger, sizeof(linger));
            if (ret == -1)
            {
                stlog_debug("SO_LINGER failed");
            }
            return ret;
        }
        //设置广播
        static int setBroadcast(int fd_socket, bool isEnable)
        {
            int opt = isEnable ? 1 : 0;
            int ret = setsockopt(fd_socket, SOL_SOCKET, SO_BROADCAST, (char *)&opt, static_cast<socklen_t>(sizeof(opt)));
            if (ret == -1)
            {
                stlog_debug("SO_BROADCAST failed");
            }
            return ret;
        };
        //设置保活
        static int setKeepAlive(int fd_socket, bool isEnable)
        {
            int opt = isEnable ? 1 : 0;
            int ret = setsockopt(fd_socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&opt, static_cast<socklen_t>(sizeof(opt)));
            if (ret == -1)
            {
                stlog_debug("SO_KEEPALIVE failed");
            }
            return ret;
        }
        //不懂，和fork进程有关系，暂时无用
        static int setCloExec(int fd_socket, bool isEnable = true)
        {
#if !defined(_WIN32)
            int flags = fcntl(fd_socket, F_GETFD);
            if (flags == -1)
            {
                stlog_debug("FD_CLOEXEC failed");
                return -1;
            }
            if (isEnable)
            {
                flags |= FD_CLOEXEC;
            }
            else
            {
                int cloexec = FD_CLOEXEC;
                flags &= ~cloexec;
            }
            int ret = fcntl(fd_socket, F_SETFD, flags);
            if (ret == -1)
            {
                stlog_debug("FD_CLOEXEC failed");
                return -1;
            }
            return 0;
#else
            return -1;
#endif
        }
        //设置不处理信号量
        static int setNoSigPipe(int fd_socket)
        {
#if defined(SO_NOSIGPIPE)
            int set = 1;
            auto ret = setsockopt(fd_socket, SOL_SOCKET, SO_NOSIGPIPE, (char *)&set, sizeof(int));
            if (ret == -1)
            {
                stlog_debug("SO_NOSIGPIPE failed");
            }
            return ret;
#else
            return -1;
#endif
        }

    public: //socket server
        // 创建tcp监听socket
        static int createTcpListenerSocket(bool isNoblock = true)
        {
            int listenerSocket = -1;
            int domain = AF_INET;       // AF_INET IPv4 | AF_INET6 IPv6
            int type = SOCK_STREAM;     // TCP使用的Socket样式：SOCK_STREAM | UDP使用的socket样式：SOCK_DGRAM
            int protocol = IPPROTO_TCP; // IPPROTO_IP:0 默认协议 | IPPROTO_TCP TCP协议 | IPPROTO_UDP UDP协议 注意：协议类型需要与socket样式匹配
            listenerSocket = socket(domain, type, protocol);
            if (listenerSocket == -1)
            {
                perror("perror");
                stlog_error("Can't create listenerSocket socket\n");
                return listenerSocket;
            }

            STL_Socket::setReuseAddr(listenerSocket, true);
            STL_Socket::setReusePort(listenerSocket, false);
            STL_Socket::setNonBlock(listenerSocket, isNoblock);
            STL_Socket::setCloExec(listenerSocket);

            // stlog_debug("create tcp listener socket [%d]\n", listenerSocket);
            return listenerSocket;
        };
        // 绑定tcp监听socket
        static int bindListenerSocket(int fd_listener, std::string ip, uint16_t port)
        {
            auto serverAddr = genSockAddr(ip, port);
            int bindRet = bind(fd_listener, (sockaddr *)&serverAddr, sizeof(serverAddr));
            if (bindRet < 0)
            {
                perror("perror");
                stlog_error("listener socket [%d] Can't bind listener_socket socket\n", fd_listener);
            }
            else
            {
                // stlog_debug("listener socket [%d] succeed bind [%s:%d]\n", fd_listener, ip.c_str(), port);
            }
            return bindRet;
        };
        // 开始tcp监听socket
        static int listenListenerSocket(int fd_listener)
        {
            int listenRet = listen(fd_listener, SOMAXCONN); // 最大连接数
            if (listenRet < 0)
            {
                perror("perror");
                stlog_debug("listener socket [%d] fd_listener socket error\n", fd_listener);
            }
            else
            {
                // stlog_debug("listener socket [%d] succeed fd_listener\n", fd_listener);
            }
            return listenRet;
        };
        typedef struct AcceptNeConnectionInfo_t
        {
            int socket;
            std::string ip;
            uint16_t port;
        } AcceptNeConnectionInfo;
        // 接收tcp客户连接
        static AcceptNeConnectionInfo acceptNewConnection(int fd_listener)
        {
            AcceptNeConnectionInfo ret;
            ret.socket = -1;
            struct sockaddr_in clientAddr;
            unsigned int clientAddrLen = sizeof(clientAddr);
            ret.socket = accept(fd_listener, (struct sockaddr *)&clientAddr, &clientAddrLen);
            stlog_info("accept socket:%d", ret.socket);
            if (ret.socket < 0)
            {
                perror("accept");
                stlog_error("accept error\n");
                return ret;
            }

            ret.ip = std::string(inet_ntoa(clientAddr.sin_addr));
            ret.port = ntohs(clientAddr.sin_port);
            return ret;
        }
    public: //tcp client
        static int cretaeTcpClientSocket(bool isNoblock)
        {
            int domain = AF_INET;       //AF_INET IPv4 | AF_INET6 IPv6
            int type = SOCK_STREAM;     // TCP使用的Socket样式：SOCK_STREAM | UDP使用的socket样式：SOCK_DGRAM
            int protocol = IPPROTO_TCP; // IPPROTO_IP:0 默认协议 | IPPROTO_TCP TCP协议 | IPPROTO_UDP UDP协议 注意：协议类型需要与socket样式匹配
            int fd = socket(domain, type, protocol);
            if (fd == -1)
            {
                stlog_error("Can't create socket socket");
                perror("create socket error:");
                return -1;
            }

            STL_Socket::setNoSigPipe(fd);
            STL_Socket::setNonBlock(fd, isNoblock);
            STL_Socket::setTcpNoDelay(fd);
            STL_Socket::setSendBuf(fd);
            STL_Socket::setRecvBuf(fd);
            STL_Socket::setLinger(fd);
            STL_Socket::setCloExec(fd);

            // stlog_info("cretaeTcpClientSocket:%d", fd);

            return fd;
        }

        static struct sockaddr_in genSockAddr(std::string ip, uint16_t port)
        {
            struct sockaddr_in sockaddr;
            memset(&sockaddr, 0, sizeof(sockaddr));
            // sockaddr.sin_addr.s_addr = INADDR_ANY;      //0.0.0.0
            // sockaddr.sin_addr.s_addr = INADDR_LOOPBACK; //127.0.0.1
            sockaddr.sin_addr.s_addr = inet_addr(ip.c_str());
            sockaddr.sin_family = AF_INET; //AF_INET IPv4 | AF_INET6 IPv6
            sockaddr.sin_port = htons(port);
            return sockaddr;
        };

        static void async_connect(std::string ip, uint16_t port, std::function<void(int /* fd_socket*/)> connect_cb)
        {
            std::thread temp(sync_connect, ip, port, connect_cb);
            temp.detach();
        }

        static void sync_connect(std::string ip, uint16_t port, std::function<void(int /* fd_socket*/)> connect_cb)
        {
            int fd_socket = STL_Socket::cretaeTcpClientSocket(false);
            if (fd_socket < 0)
            {
                if (connect_cb)
                    connect_cb(-1);
                return;
            }
            auto serverAddr = genSockAddr(ip, port);
            int rc = connect(fd_socket, (sockaddr *)&serverAddr, sizeof(serverAddr));
            if (rc == 0)
            {
                if (connect_cb)
                    connect_cb(fd_socket);
                return;
            }
            else
            {
                STL_SAFE_CLOSE(fd_socket);
                if (connect_cb)
                    connect_cb(-1);
                return;
            }
        }
        static void async_select_connect(std::string ip, uint16_t port, std::function<void(int /* fd_socket*/)> connect_cb, time_t msTimeout = 3000)
        {
            std::thread temp(sync_select_connect, ip, port, connect_cb, msTimeout);
            temp.detach();
        }
        static void sync_select_connect(std::string ip, uint16_t port, std::function<void(int /* fd_socket*/)> connect_cb, time_t msTimeout = 3000)
        {
            int fd_socket = STL_Socket::cretaeTcpClientSocket(true);
            if (fd_socket < 0)
            {
                if (connect_cb)
                    connect_cb(-1);
                return;
            }
            auto serverAddr = genSockAddr(ip, port);
            int rc = connect(fd_socket, (sockaddr *)&serverAddr, sizeof(serverAddr));
            /*阻塞情况下linux系统默认超时时间为75s*/
            if (rc == -1)
            {
                if (errno == EINPROGRESS)
                {
                    /*正在处理连接*/
                    fd_set fdr;              // 声明读集合
                    fd_set fdw;              // 声明写集合
                    FD_ZERO(&fdr);           // 清空读集合
                    FD_ZERO(&fdw);           // 清空写结合
                    FD_SET(fd_socket, &fdr); // socket添加到读集合中
                    FD_SET(fd_socket, &fdw); // socket添加到写集合中
                    struct timeval timeout;
                    timeout.tv_sec = msTimeout / 1000;
                    timeout.tv_usec = msTimeout % 1000;
                    rc = select(fd_socket + 1, &fdr, &fdw, NULL, &timeout);
                    /*select调用失败*/
                    if (rc < 0)
                    {
                        stlog_error("connect error:%s", strerror(errno));
                        STL_SAFE_CLOSE(fd_socket);
                        if (connect_cb)
                            connect_cb(-1);
                        return;
                    }

                    /*连接超时*/
                    if (rc == 0)
                    {
                        stlog_error("Connect timeout.");
                        STL_SAFE_CLOSE(fd_socket);
                        if (connect_cb)
                            connect_cb(-1);
                        return;
                    }
                    /*[1] 当连接成功建立时，描述符变成可写,rc=1*/
                    if (rc == 1 && FD_ISSET(fd_socket, &fdw))
                    {
                        // stlog_trace("Connect success");
                        if (connect_cb)
                            connect_cb(fd_socket);
                        return;
                    }
                    /*[2] 当连接建立遇到错误时，描述符变为即可读，也可写，rc=2 遇到这种情况，可调用getsockopt函数*/
                    if (rc == 2)
                    {
                        int err = 0;
                        int errlen = sizeof(err);
                        if (getsockopt(fd_socket, SOL_SOCKET, SO_ERROR, &err, (socklen_t *)(&errlen)) == -1)
                        {
                            stlog_error("getsockopt(SO_ERROR): %s", strerror(errno));
                            STL_SAFE_CLOSE(fd_socket);
                            if (connect_cb)
                                connect_cb(-1);
                            return;
                        }

                        if (err)
                        {
                            errno = err;
                            stlog_warn("connect error:%s", strerror(errno));
                            STL_SAFE_CLOSE(fd_socket);
                            if (connect_cb)
                                connect_cb(-1);
                            return;
                        }
                    }
                }
                else
                {
                    stlog_error("connect failed, error:%s.", strerror(errno));
                    STL_SAFE_CLOSE(fd_socket);
                    if (connect_cb)
                        connect_cb(-1);
                    return;
                }
            }
            else
            {
                if (connect_cb)
                    connect_cb(fd_socket);
                return;
            }
        }

        static void async_epoll_connect(std::string ip, uint16_t port, std::function<void(int /* fd_socket*/)> connect_cb, int msTimeout = 3000)
        {
            std::thread temp(sync_epoll_connect, ip, port, connect_cb, msTimeout);
            temp.detach();
        }
        static void sync_epoll_connect(std::string ip, uint16_t port, std::function<void(int /* fd_socket*/)> connect_cb, int msTimeout = 3000)
        {
            int fd_socket = STL_Socket::cretaeTcpClientSocket(true);
            if (fd_socket < 0)
            {
                if (connect_cb)
                    connect_cb(-1);
                return;
            }
            int fd_epoll = STL_Socket::createEpoll();
            if (fd_epoll == -1)
            {
                STL_SAFE_CLOSE(fd_socket);
                if (connect_cb)
                    connect_cb(-1);
                return;
            }
            auto serverAddr = genSockAddr(ip, port);
            int rc = connect(fd_socket, (sockaddr *)&serverAddr, sizeof(serverAddr));
            /*阻塞情况下linux系统默认超时时间为75s*/
            if (rc == -1)
            {
                struct epoll_event event;
                event.events = (uint32_t)(EPOLLIN | EPOLLOUT | EPOLLET);
                event.data.fd = fd_socket;
                epoll_ctl(fd_epoll, EPOLL_CTL_ADD, fd_socket, &event);
                struct epoll_event eventArr[EPOLL_MAX_EVENTS];
                // int status, err;
                // socklen_t len;
                int err = 0;
                int errlen = sizeof(err);
                int n = epoll_wait(fd_epoll, eventArr, EPOLL_MAX_EVENTS, msTimeout); // 300ms
                for (int i = 0; i < n; i++)
                {
                    epoll_event ev = eventArr[i];
                    int events = ev.events;
                    if (events & EPOLLERR)
                    {
                        if (getsockopt(fd_socket, SOL_SOCKET, SO_ERROR, &err, (socklen_t *)(&errlen)) == -1)
                        {
                            stlog_error("getsockopt(SO_ERROR): %s", strerror(errno));
                            epoll_ctl(fd_epoll, EPOLL_CTL_DEL, fd_socket, NULL);
                            STL_SAFE_CLOSE(fd_epoll);
                            STL_SAFE_CLOSE(fd_socket);
                            if (connect_cb)
                                connect_cb(-1);
                            return;
                        }
                    }
                    if (events & EPOLLIN)
                    {
                        // stlog_error("in event fire.");
                    }
                    if (events & EPOLLOUT)
                    {
                        // stlog_error("out event fire.");
                    }
                }
                epoll_ctl(fd_epoll, EPOLL_CTL_DEL, fd_socket, NULL);
                STL_SAFE_CLOSE(fd_epoll);
                if (connect_cb)
                    connect_cb(fd_socket);
                return;
            }
            else
            {
                if (connect_cb)
                    connect_cb(fd_socket);
                return;
            }
        }

    public: //epoll
        static int createEpoll()
        {
            int fd = epoll_create1(0);
            if (fd == -1)
            {
                perror("epoll_create fail");
                stlog_error("epoll_create fail");
            }
            // stlog_info("createEpoll:%d", fd);
            return fd;
        };

        static int addListenerSocketToEpoll(int fd_epoll, int fd_listener)
        {
            /*添加listener_socket*/
            struct epoll_event ev;
            ev.events = EPOLLIN;
            ev.data.fd = fd_listener;
            int ret = epoll_ctl(fd_epoll, EPOLL_CTL_ADD, fd_listener, &ev);
            if (ret < 0)
            {
                perror("epoll_ctl: fd_listener");
                stlog_error("addListenerEpoll::epoll_ctl[EPOLL_CTL_ADD] error\n");
            }
            return ret;
        };

        static int removeListenerSocketToEpoll(int fd_epoll, int fd_listener)
        {
            /*删除listener_socket*/
            int ret = epoll_ctl(fd_epoll, EPOLL_CTL_DEL, fd_listener, NULL);
            if (ret < 0)
            {
                perror("epoll_ctl: fd_listener");
                stlog_error("delListenerEpoll::epoll_ctl[EPOLL_CTL_DEL] error\n");
            }
            return ret;
        };

        static int addConnectionSocketToEpoll(int fd_epoll, int fd_connection)
        {
            // stlog_info("addConnectionSocketToEpoll(%d,%d)", fd_epoll, fd_connection);

            setNonBlock(fd_connection);
            struct epoll_event ev;
            ev.events = EPOLLIN | EPOLLET;
            ev.data.fd = fd_connection;
            int ret = epoll_ctl(fd_epoll, EPOLL_CTL_ADD, fd_connection, &ev);
            if (ret < 0)
            {
                perror("epoll_ctl fd_connection");
                stlog_error("addConnectEpoll::epoll_ctl[EPOLL_CTL_ADD] error\n");
            }
            return ret;
        };

        static int removeConnectionSocketFromEpoll(int fd_epoll, int fd_connection)
        {
            // stlog_info("removeConnectionSocketFromEpoll(%d,%d)", fd_epoll, fd_connection);
            int ret = epoll_ctl(fd_epoll, EPOLL_CTL_DEL, fd_connection, NULL);
            if (ret < 0)
            {
                perror("epoll_ctl fd_connection");
                stlog_error("epoll_ctl[EPOLL_CTL_DEL] error\n");
            }
            return ret;
        };

        static std::vector<int> getEpollEvent(int fd_epoll)
        {
            /*epoll_wait*/
            int nfds;
            struct epoll_event events[EPOLL_MAX_EVENTS];

            std::vector<int> ret;
            nfds = epoll_wait(fd_epoll, events, EPOLL_MAX_EVENTS, -1);
            auto _errno = errno;
            // stlog_info("epoll_wait[%d,%d]", nfds, _errno);
            /*
            我们在利用 gdb 调试带有 epoll_wait select sem_wat 的多线程代码的时候可能会出现非正常返回 -1 的情况，错误原因是：Interrupted system call。这是由于gdb调试的时候会在断点处插入一条中断指令,当程序执行到该断点处的时候会发送一个SIGTRAP信号,程序转去执行中断相应,进而gdb让程序停下来进行调试. 对于sem_wait\wait\read等会阻塞的函数在调试时,如果阻塞,都可能会收到调试器发送的信号,而返回非0值.
            */
            // 照着ZLToolKit处理逻辑搞得，人家就不相信epoll会有异常
            if (nfds <= 0)
            {
                //超时或被打断
                return ret;
            }
            // if (errno == EINTR)
            // {
            //     continue;
            // }
            // if (nfds == -1 && errno != EINTR)
            // {
            //     perror("epoll_wait Error");
            //     stlog_fatal("epoll_wait error\n");
            //     STL_SAFE_CLOSE(m_listener_socket);
            //     STL_SAFE_CLOSE(m_epoll_fd);
            //     return;
            // }

            for (size_t i = 0; i < nfds; i++)
            {
                ret.push_back(events[i].data.fd);
            }

            return ret;
        }
    };
} // STL

#endif //__STL_SOCKET_H__
